package  
de.popforge.audio
{
	import __AS3__.vec.Vector;
	
	import de.popforge.audio.driver.AudioDriver;
	import de.popforge.audio.driver.IAudioProcessor;		

	/**
	 * Random Polyphone Synthesizer - 8Bitstyle
	 * 
	 * Simple and VERY nasty implementation of a Karplus-Strong Delayline
	 * 
	 * @author Andre Michelle
	 */
	public class PlayString
		implements IAudioProcessor
	{
		private const BPM: Number = 60;
		private const notes: Array = [ 1, 3, 6, 8, 10 ];
		private const baseOffset: Array = [ 0, 2, 7 ];
		
		private var _voices: Vector.<PlayVoice>;
		private var _voiceIndex: int;
		private var _activeVoices: Vector.<PlayVoice>;
		private var _base: int;
		private var _position: Number;
		
		public function PlayString()
		{
			init();
		}

		private function init(): void
		{
			var i: int;
			
			//-- CREATE A VOICE POOL
			_voices = new Vector.<PlayVoice>( 64, true );
			_voiceIndex = 0;
			
			for( i = 0 ; i < 64 ; ++i )
				_voices[i] = new PlayVoice();
				
			//-- WHERE THE ACTIVE VOICES ARE STORED
			_activeVoices = new Vector.<PlayVoice>();
			
			//-- INIT ENGINE
			_position = 0.0;
			_base = 0;
		}
		
		public function process( buffer: Vector.<Vector.<Number>> ): void
		{
			clearBuffer( buffer );			
			processEvents();
			processAudio( buffer );
		}
		
		private function clearBuffer( buffer: Vector.<Vector.<Number>> ): void
		{
			var le: Vector.<Number> = buffer[0];
			var ri: Vector.<Number> = buffer[1];
			
			var i: int = 0;
			var n: int = AudioDriver.SIZE;
			
			for( ; i < n ; ++i )
			{
				le[i] = 0.0;
				ri[i] = 0.0;
			}
		}

		private function processAudio( buffer: Vector.<Vector.<Number>> ): void
		{
			var i: int;
			var n: int = _activeVoices.length;
			
			for( i = n - 1 ; i > -1 ; --i )
			{
				if( _activeVoices[i].processAudioAdd( buffer ) )
					_activeVoices.splice( i, 1 );
			}
		}

		private function processEvents(): void
		{
			var posA: Number = _position;
			var posB: Number = _position + AudioDriver.SIZE * ( BPM * 9.448223733938e-8 );
			
			var stepLength: int = 1 << ( 3 + int( Math.random() * 3 ) );
			
			var position: Number = int( posB * stepLength ) / stepLength; // SEARCH WINDOW
			var stepIndex: int = position * 16;
			var offset: int;
			var note: int;
			var octave: int;
			var pan: Number;
			var volume: Number;
			var numNotes: int;
			
			while( position < posB )
			{
				if( position > posA )
				{
					numNotes = Math.random() * 4;
					
					if( numNotes )
					{
						offset = ( position - _position ) * ( 10584000.0 / BPM ); // BLOCK OFFSET IN SAMPLES

						if( stepIndex % 16 == 0 )
							_base = baseOffset[ int( Math.random() * 3 ) ];
						
						while( --numNotes > -1 )
						{
							octave = 3 + Math.random() * 4;
							
							note = notes[ int( Math.random() * 5 ) ] + octave * 12 + _base;
							
							pan = Math.random() - Math.random();
							
							volume = .5 + Math.random() * .1;

							createPlayVoice( offset, note, pan, volume );
						}
					}
				}
				
				position += 1 / stepLength;
				++stepIndex;
			}
			
			//-- ADVANCE ENGINE POSITION
			_position = posB;
		}

		private function createPlayVoice( offset: int, note: int, pan: Number, volume: Number ): void
		{
			var voice: PlayVoice = _voices[_voiceIndex];
			
			//-- WE SHOULD ENSURE THAT THE VOICE ISN'T IN USE! (But I don't here)
			if( ++_voiceIndex == _voices.length )
				_voiceIndex = 0;
				
			voice.restartVoice( offset, note, pan, volume );
			
			_activeVoices.push( voice );
		}
	}
}

import de.popforge.audio.driver.AudioDriver;
import __AS3__.vec.Vector;

class PlayVoice
{
	private const lineL: Vector.<Number> = new Vector.<Number>( 1024, true );
	private const lineR: Vector.<Number> = new Vector.<Number>( 1024, true );
	
	private var _length: int;
	private var _writeIndex: int;
	private var _displaceL: int;
	private var _displaceR: int;
	private var _duration: int;
	
	private var _offset: int;
	private var _gainL: Number;
	private var _gainR: Number;
	
	// LOWPASS 1 Pole
	private var _velL: Number;
	private var _outL: Number;
	private var _velR: Number;
	private var _outR: Number;
	
	public function PlayVoice()
	{
	}
	
	public function restartVoice( offset: int, note: int, pan: Number, volume: Number ): void
	{
		_offset = offset;
		_length = 44100 / ( 440 * Math.pow( 2, note / 12 - 6 ) );
		_displaceL = _length * ( .99 + Math.random() * .01 ) - 1;
		_displaceR = _length * ( .99 + Math.random() * .01 ) - 1;
		_writeIndex = 0;
		_gainL = ( 1 - pan ) * volume;
		_gainR = ( pan + 1 ) * volume;
		_velL = _velR = 0.0;
		_outL = _outR = 0.0;
		
		for( var i: int = 0 ; i < 1024 ; ++i )
		{
			lineL[i] = Math.random() - Math.random();
			lineR[i] = Math.random() - Math.random();
		}
		
		_duration = 32000;
	}

	public function processAudioAdd( buffer: Vector.<Vector.<Number>> ): Boolean
	{
		var readIndex: int;
		var ampL: Number;
		var ampR: Number;
		
		var le: Vector.<Number> = buffer[0];
		var ri: Vector.<Number> = buffer[1];
		
		var i: int = _offset;
		var n: int = AudioDriver.SIZE;
		
		for( ; i < n ; ++i )
		{
			_offset = 0;

			readIndex = _writeIndex - _displaceL;
			if( readIndex < 0 )
				readIndex += _length;
			
			_velL *= .1;
			_velL += ( lineL[readIndex] - _outL ) * .24;
			_outL += _velL;
			
			ampL = lineL[_writeIndex] = _outL;
			
			readIndex = _writeIndex - _displaceR;
			if( readIndex < 0 )
				readIndex += _length;
			
			_velR *= .1;
			_velR += ( lineL[readIndex] - _outR ) * .24;
			_outR += _velR;
			
			ampR = lineR[_writeIndex] = _outR;
			
			if( _duration < 512 )
			{
				ampL *= _duration * 0.001953125;
				ampR *= _duration * 0.001953125;
			}
			
			if( --_duration == 0 )
				return true;

			//-- WRITE TO BUFFER
			le[i] += ampL * _gainL;
			ri[i] += ampR * _gainR;
			
			if( ++_writeIndex == _length )
				_writeIndex = 0;
		}
		
		return false;
	}
}